/***********************************************************************************
 *  执行jpeg编码任务的优先级不能高于 jpeg encode_dma中任务的优先级, 否则会导致Jpeg断裂
 *  编码时, 其它任务\中断等不可以访问编码用的内存片区, 否则有概率导致Jpeg断裂
 *  Jpeg断裂现象为最后一包没有编码, 也会返回, 形成一个按32位对齐的长度
 */

/* Includes ------------------------------------------------------------------*/
#include <string.h>
#include "main.h"
#include "Utilities_JPEG/jpeg_utils.h"
#include "tx_api.h"

/* Private includes ----------------------------------------------------------*/

/* Private typedef -----------------------------------------------------------*/
typedef struct
{
  uint8_t State;
  uint8_t *DataBuffer;
  uint32_t DataBufferSize;

} JPEG_Data_BufferTypeDef;

/* Private define ------------------------------------------------------------*/
#define TASK_STACK_SIZE (1024)

#define RGB_IMAGE_WIDTH ((uint32_t)640)
#define RGB_IMAGE_HEIGHT ((uint32_t)480)

#define RGB_IMAGE_SIZE ((uint32_t)RGB_IMAGE_HEIGHT * RGB_IMAGE_WIDTH)

#define GRAY_444_BLOCK_SIZE 64 /* GrayScale MCU : 1 8x8 block of Y */
#define BYTES_PER_PIXEL 1
#define JPEG_BYTES_PER_PIXEL 1

#define JPEG_CHROMA_SAMPLING JPEG_420_SUBSAMPLING /* Select Chroma Sampling: JPEG_420_SUBSAMPLING, JPEG_422_SUBSAMPLING, JPEG_444_SUBSAMPLING   */
#define JPEG_COLOR_SPACE JPEG_YCBCR_COLORSPACE    /* Select Color Space: JPEG_YCBCR_COLORSPACE, JPEG_GRAYSCALE_COLORSPACE, JPEG_CMYK_COLORSPACE */
#define JPEG_IMAGE_QUALITY 90                     /* Set Image Quality for Jpeg Encoding */
#define MAX_INPUT_WIDTH 640                       /* Set the Maximum of BMP images Width to be tested */
#define MAX_INPUT_LINES (16)                      /* Set Input buffer lines to 16 for YCbCr420, and 8 for YCbCr422 and YCbCr444 (to save RAM space) */

#define CHUNK_SIZE_IN ((uint32_t)(MAX_INPUT_WIDTH * BYTES_PER_PIXEL * MAX_INPUT_LINES))
#define CHUNK_SIZE_OUT ((uint32_t)(4096))

#define JPEG_BUFFER_EMPTY 0
#define JPEG_BUFFER_FULL 1

/* Private macro -------------------------------------------------------------*/

/* Global variables ---------------------------------------------------------*/

/* External function ---------------------------------------------------------*/
extern void Error_Handler();

/* External variables ---------------------------------------------------------*/
extern JPEG_HandleTypeDef hjpeg;

/* Private variables ---------------------------------------------------------*/
static JPEG_RGBToYCbCr_Convert_Function pRGBToYCbCr_Convert_Function;

static uint8_t MCU_Data_IntBuffer0[CHUNK_SIZE_IN];
static uint8_t JPEG_Data_OutBuffer0[CHUNK_SIZE_OUT];

static JPEG_Data_BufferTypeDef Jpeg_OUT_BufferTab = {JPEG_BUFFER_EMPTY, JPEG_Data_OutBuffer0, 0};
static JPEG_Data_BufferTypeDef Jpeg_IN_BufferTab = {JPEG_BUFFER_EMPTY, MCU_Data_IntBuffer0, 0};

static uint32_t MCU_TotalNb = 0;
static uint32_t MCU_BlockIndex = 0;
static uint32_t Jpeg_HWEncodingEnd = 0;

static __IO uint32_t Output_Is_Paused = 0;
static __IO uint32_t Input_Is_Paused = 0;

static JPEG_ConfTypeDef Conf;

static uint32_t RGB_InputImageIndex;
static uint32_t RGB_InputImageSize_Bytes;
static uint32_t RGB_InputImageAddress;
static uint32_t RGB_InputImageAddress2;

static uint32_t jpeg_buffer_index = 0;
static uint8_t *jpeg_buffer_start;

/*****************AzThread******************/
static TX_SEMAPHORE jpeg_semaphore_finish;
static TX_SEMAPHORE jpeg_semaphore_input;
static TX_SEMAPHORE jpeg_semaphore_output;

static TX_THREAD tx_task_input;
static TX_THREAD tx_task_output;
static uint8_t task_stack_input[TASK_STACK_SIZE];
static uint8_t task_stack_output[TASK_STACK_SIZE];
/* Private function prototypes -----------------------------------------------*/

/* Function definitions -----------------------------------------------*/

void JPEG_Encode_DMA(JPEG_HandleTypeDef *hjpeg, uint32_t RGBImageBufferAddress, uint32_t RGBImageBufferAddress2, uint32_t RGBImageSize_Bytes, uint32_t JPEG_BufferAddress)
{
  uint32_t DataBufferSize = 0;

  jpeg_buffer_start = (uint8_t *)JPEG_BufferAddress;

  /* Reset all Global variables */
  MCU_TotalNb = 0;
  MCU_BlockIndex = 0;
  Jpeg_HWEncodingEnd = 0;
  Output_Is_Paused = 0;
  Input_Is_Paused = 0;

  /* Get RGB Info */
  JPEG_ConfTypeDef *pInfo = &Conf;

  /* Read Images Sizes */
  pInfo->ImageWidth = RGB_IMAGE_WIDTH;
  pInfo->ImageHeight = RGB_IMAGE_HEIGHT;

  /* Jpeg Encoding Setting to be set by users */
  pInfo->ChromaSubsampling = JPEG_444_SUBSAMPLING;
  pInfo->ColorSpace = JPEG_GRAYSCALE_COLORSPACE;
  pInfo->ImageQuality = JPEG_IMAGE_QUALITY;

  my_JPEG_GetEncodeColorConvertFunc(&Conf, &pRGBToYCbCr_Convert_Function, &MCU_TotalNb);

  /* Clear Output Buffer */
  Jpeg_OUT_BufferTab.DataBufferSize = 0;
  Jpeg_OUT_BufferTab.State = JPEG_BUFFER_EMPTY;

  /* Fill input Buffers */
  RGB_InputImageIndex = 0;
  RGB_InputImageAddress = RGBImageBufferAddress;
  RGB_InputImageAddress2 = RGBImageBufferAddress2;
  RGB_InputImageSize_Bytes = RGBImageSize_Bytes;
  DataBufferSize = Conf.ImageWidth * MAX_INPUT_LINES * BYTES_PER_PIXEL;

  if (RGB_InputImageIndex < RGB_InputImageSize_Bytes)
  {
    /* Pre-Processing */
    MCU_BlockIndex += pRGBToYCbCr_Convert_Function((uint8_t *)(RGB_InputImageAddress + RGB_InputImageIndex), Jpeg_IN_BufferTab.DataBuffer, 0, DataBufferSize, (uint32_t *)(&Jpeg_IN_BufferTab.DataBufferSize));
    Jpeg_IN_BufferTab.State = JPEG_BUFFER_FULL;

    RGB_InputImageIndex += DataBufferSize;
  }

  /* Fill Encoding Params */
  HAL_JPEG_ConfigEncoding(hjpeg, &Conf);

  /* Start JPEG encoding with DMA method */
  HAL_JPEG_Encode_DMA(hjpeg, Jpeg_IN_BufferTab.DataBuffer, Jpeg_IN_BufferTab.DataBufferSize, Jpeg_OUT_BufferTab.DataBuffer, CHUNK_SIZE_OUT);
}

/**
 * @brief JPEG Output Data BackGround processing .
 * @param hjpeg: JPEG handle pointer
 * @retval 1 : if JPEG processing has finiched, 0 : if JPEG processing still ongoing
 */
uint32_t JPEG_EncodeOutputHandler(JPEG_HandleTypeDef *hjpeg)
{
  // uint32_t bytesWritefile = 0;

  if (Jpeg_OUT_BufferTab.State == JPEG_BUFFER_FULL)
  {
    // f_write(pJpegFile, Jpeg_OUT_BufferTab.DataBuffer, Jpeg_OUT_BufferTab.DataBufferSize, (UINT *)(&bytesWritefile));
    memcpy(jpeg_buffer_start + jpeg_buffer_index, Jpeg_OUT_BufferTab.DataBuffer, Jpeg_OUT_BufferTab.DataBufferSize);
    jpeg_buffer_index += Jpeg_OUT_BufferTab.DataBufferSize;

    Jpeg_OUT_BufferTab.State = JPEG_BUFFER_EMPTY;
    Jpeg_OUT_BufferTab.DataBufferSize = 0;

    if (Jpeg_HWEncodingEnd != 0)
    {
      return 1;
    }
    // else if ((Output_Is_Paused == 1) && (Jpeg_OUT_BufferTab.State == JPEG_BUFFER_EMPTY))
    else if (Output_Is_Paused == 1) // ssjhs
    {
      Output_Is_Paused = 0;
      HAL_JPEG_Resume(hjpeg, JPEG_PAUSE_RESUME_OUTPUT);
    }
  }

  return 0;
}

/**
 * @brief JPEG Input Data BackGround Preprocessing .
 * @param hjpeg: JPEG handle pointer
 * @retval None
 */
void JPEG_EncodeInputHandler(JPEG_HandleTypeDef *hjpeg)
{
  uint32_t addr, index;
  uint32_t DataBufferSize = Conf.ImageWidth * MAX_INPUT_LINES * BYTES_PER_PIXEL;

  if ((Jpeg_IN_BufferTab.State == JPEG_BUFFER_EMPTY) && (MCU_BlockIndex <= MCU_TotalNb))
  {
    /* Read and reorder lines from RGB input and fill data buffer */
    if (RGB_InputImageIndex < RGB_InputImageSize_Bytes)
    {
      if (RGB_InputImageIndex >= RGB_InputImageSize_Bytes / 2)
      {
        index = RGB_InputImageIndex - RGB_InputImageSize_Bytes / 2;
        addr = RGB_InputImageAddress2;
      }
      else
      {
        index = RGB_InputImageIndex;
        addr = RGB_InputImageAddress;
      }

      /* Pre-Processing */
      // MCU_BlockIndex += pRGBToYCbCr_Convert_Function((uint8_t *)(RGB_InputImageAddress + RGB_InputImageIndex), Jpeg_IN_BufferTab.DataBuffer, 0, DataBufferSize, (uint32_t *)(&Jpeg_IN_BufferTab.DataBufferSize));
      MCU_BlockIndex += pRGBToYCbCr_Convert_Function((uint8_t *)(addr + index), Jpeg_IN_BufferTab.DataBuffer, 0, DataBufferSize, (uint32_t *)(&Jpeg_IN_BufferTab.DataBufferSize));
      Jpeg_IN_BufferTab.State = JPEG_BUFFER_FULL;
      RGB_InputImageIndex += DataBufferSize;

      if (Input_Is_Paused == 1)
      {
        Input_Is_Paused = 0;
        HAL_JPEG_ConfigInputBuffer(hjpeg, Jpeg_IN_BufferTab.DataBuffer, Jpeg_IN_BufferTab.DataBufferSize);

        HAL_JPEG_Resume(hjpeg, JPEG_PAUSE_RESUME_INPUT);
      }
    }
    else
    {
      MCU_BlockIndex++;
    }
  }
}

/**
 * @brief JPEG Get Data callback
 * @param hjpeg: JPEG handle pointer
 * @param NbEncodedData: Number of encoded (consummed) bytes from input buffer
 * @retval None
 */
void HAL_JPEG_GetDataCallback(JPEG_HandleTypeDef *hjpeg, uint32_t NbEncodedData)
{
  if (NbEncodedData == Jpeg_IN_BufferTab.DataBufferSize)
  {
    Jpeg_IN_BufferTab.State = JPEG_BUFFER_EMPTY;
    Jpeg_IN_BufferTab.DataBufferSize = 0;

    HAL_JPEG_Pause(hjpeg, JPEG_PAUSE_RESUME_INPUT);
    Input_Is_Paused = 1;

    tx_semaphore_put(&jpeg_semaphore_input);
  }
  else
  {
    HAL_JPEG_ConfigInputBuffer(hjpeg, Jpeg_IN_BufferTab.DataBuffer + NbEncodedData, Jpeg_IN_BufferTab.DataBufferSize - NbEncodedData);
  }
}

/**
 * @brief JPEG Data Ready callback
 * @param hjpeg: JPEG handle pointer
 * @param pDataOut: pointer to the output data buffer
 * @param OutDataLength: length of output buffer in bytes
 * @retval None
 */
void HAL_JPEG_DataReadyCallback(JPEG_HandleTypeDef *hjpeg, uint8_t *pDataOut, uint32_t OutDataLength)
{
  Jpeg_OUT_BufferTab.State = JPEG_BUFFER_FULL;
  Jpeg_OUT_BufferTab.DataBufferSize = OutDataLength;

  HAL_JPEG_Pause(hjpeg, JPEG_PAUSE_RESUME_OUTPUT);
  Output_Is_Paused = 1;

  HAL_JPEG_ConfigOutputBuffer(hjpeg, Jpeg_OUT_BufferTab.DataBuffer, CHUNK_SIZE_OUT);

  tx_semaphore_put(&jpeg_semaphore_output);
}

/**
 * @brief  JPEG Error callback
 * @param hjpeg: JPEG handle pointer
 * @retval None
 */
void HAL_JPEG_ErrorCallback(JPEG_HandleTypeDef *hjpeg)
{
  Error_Handler();
}

/*
 * @brief JPEG Decode complete callback
 * @param hjpeg: JPEG handle pointer
 * @retval None
 */
void HAL_JPEG_EncodeCpltCallback(JPEG_HandleTypeDef *hjpeg)
{
  Jpeg_HWEncodingEnd = 1;
  tx_semaphore_put(&jpeg_semaphore_finish);
}

static void jpeg_input_task_entry(ULONG arg)
{
  while (1)
  {
    tx_semaphore_get(&jpeg_semaphore_input, TX_WAIT_FOREVER);
    JPEG_EncodeInputHandler(&hjpeg);
  }
}

static void jpeg_output_task_entry(ULONG arg)
{
  while (1)
  {
    tx_semaphore_get(&jpeg_semaphore_output, TX_WAIT_FOREVER);
    JPEG_EncodeOutputHandler(&hjpeg);
  }
}

/* Public Function definitions -----------------------------------------------*/
void jpeg_init()
{
  tx_semaphore_create(&jpeg_semaphore_finish, "jpeg_finish", 0);
  tx_semaphore_create(&jpeg_semaphore_input, "jpeg_input", 0);
  tx_semaphore_create(&jpeg_semaphore_output, "jpeg_output", 0);

  tx_thread_create(&tx_task_input, "jpeg input task", jpeg_input_task_entry, 0,
                   task_stack_input, TASK_STACK_SIZE,
                   0u, 0u, TX_NO_TIME_SLICE, TX_AUTO_START);

  tx_thread_create(&tx_task_output, "jpeg output task", jpeg_output_task_entry, 0,
                   task_stack_output, TASK_STACK_SIZE,
                   0u, 0u, TX_NO_TIME_SLICE, TX_AUTO_START);
}

/**
 * @brief  JPEG Encode function
 *         此过程不能被长时间打断, 否则会导致中断覆盖, 导致数据丢失
 * @param  p_image_data : image data pointer
 * @param  p_jpeg_buffer : jpeg buffer pointer
 * @retval jpeg encode length
 */
uint32_t jpeg_encode(uint8_t *p_image_data, uint8_t *p_image_data2, uint8_t *p_jpeg_buffer)
{
  jpeg_buffer_index = 0;
  JPEG_Encode_DMA(&hjpeg, (uint32_t)p_image_data, (uint32_t)p_image_data2, RGB_IMAGE_SIZE, (uint32_t)p_jpeg_buffer);
  tx_semaphore_get(&jpeg_semaphore_finish, TX_WAIT_FOREVER);

  // uint32_t JpegEncodeProcessing_End = 0;
  // do
  // {
  //   JPEG_EncodeInputHandler(&hjpeg);
  //   JpegEncodeProcessing_End = JPEG_EncodeOutputHandler(&hjpeg);

  // } while (JpegEncodeProcessing_End == 0);

  return jpeg_buffer_index;
}

/* Testbench ------------------------------------------------------------*/
// #include "DWT.h"

// __attribute__((section(".sdram_section"))) __ALIGNED(4) static uint8_t jpeg_buffer[0x10000];

// static uint32_t t0, t1;

// void jpeg_test()
// {
//   extern uint8_t image_data[RGB_IMAGE_WIDTH * RGB_IMAGE_HEIGHT];

//   jpeg_init();
//   memset(jpeg_buffer, 0xff, sizeof(jpeg_buffer));

//   uint32_t length;
//   UNUSED(length);

//   DWT_INIT;
//   t0 = DWT_CNT;
//   length = jpeg_encode(&image_data[0], &jpeg_buffer[0]);
//   t1 = DWT_CNT;
// }
